package pl.agh.pp.zephyr.algorithms;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import pl.agh.pp.zephyr.algorithms.Command.ArgKey;
import pl.agh.pp.zephyr.algorithms.Command.TYPE;
import pl.agh.pp.zephyr.model.H323Area;
import pl.agh.pp.zephyr.model.Mcu;
import pl.agh.pp.zephyr.model.Room;

/**
 * Pierwsza wersja algorytmu - jak w dokumentacji.
 * 
 * @author piotrek&przemek
 * */
public class DistributedRoutingAlgorithm implements RoutingAlgorithm {

	@Override
	public List<Command> calculateRoute(RouteRequest request, H323Area area) {
		Command c;
		List<Command> result = new LinkedList<Command>();
		String requestedRoomName = request.getCalled().getAlias();
		Room requestedRoom = null;
		Mcu optimalMCU = null;
		// TODO: optimized room search
		for (Room room : area.rooms) {
			if (room.alias.equals(requestedRoomName)) {
				requestedRoom = room;
				break;
			}
		}
		// room not yet created
		if (requestedRoom == null) {
			// best MCU in whole net
			optimalMCU = getOptimalMCU(area.mcus);
			if (optimalMCU != null) {
				c = new Command(TYPE.CREATE_ROOM);
				c.params.put(ArgKey.CREATE_ROOM_MCU_HASH, optimalMCU.hashCode());
				c.params.put(ArgKey.CREATE_ROOM_ROOM_ALIAS, requestedRoomName);
				result.add(c);
			}
		} else {
			// room already created - search optimal MCU within host's area
			optimalMCU = getOptimalMCU(requestedRoom.mcus);
			if (optimalMCU == null) {
				// no optimal MCU with room already present - check whole
				// workgroup
				optimalMCU = getOptimalMCU(area.mcus);
				if (optimalMCU != null) {
					// connect room to new MCU
					c = new Command(TYPE.CONNECT_ROOM_TO_MCU);
					c.params.put(ArgKey.CONNECT_ROOM_TO_MCU_MCU_HASH,
							optimalMCU.hashCode());
					c.params.put(ArgKey.CONNECT_ROOM_TO_MCU_ROOM_HASH,
							requestedRoom.hashCode());
					/*
					 * c.params.put(ArgKey.ROUTE_SIMPLE_CALL_REF,
					 * request.getCallRef());
					 */
					result.add(c);
					// TODO: link MCUs
					c = new Command(TYPE.CONNECT_ROOMS);
					c.params.put(ArgKey.CONNECT_ROOMS_MCU_FROM_HASH,
							optimalMCU.hashCode());
					c.params.put(ArgKey.CONNECT_ROOMS_MCU_TO_HASH,
							requestedRoom.mcus.toArray()[0].hashCode());
					c.params.put(ArgKey.CONNECT_ROOMS_ROOM_HASH,
							requestedRoom.hashCode());
					result.add(c);/*
								 * CONNECT_ROOMS_MCU_FROM_HASH,
								 * CONNECT_ROOMS_MCU_TO_HASH,
								 * CONNECT_ROOMS_ROOM_NAME
								 */
				}
			}
		}
		if (optimalMCU == null) {
			// reject call - no free space
			c = new Command(TYPE.ROUTE_REJECT);
			c.params.put(ArgKey.ROUTE_REJECT_CALL_REF, request.getCallRef());
			c.params.put(ArgKey.ROUTE_REJECT_CALLING_EP_ID,
					request.getCallingEndpointId());
		} else {
			/*
			if (request.getCaller().ip.equals(optimalMCU.ip)) {
				// simple routing to host
				c = new Command(TYPE.ROUTE_SIMPLE);
				c.params.put(ArgKey.ROUTE_SIMPLE_ALIAS, optimalMCU.prefix
						+ requestedRoomName);
				c.params.put(ArgKey.ROUTE_SIMPLE_CALLING_ENDPOINT_ID,
						request.getCallingEndpointId());
				c.params.put(ArgKey.ROUTE_SIMPLE_CALL_REF, request.getCallRef());
			} else {*/
				// route to gateway
				c = new Command(TYPE.ROUTE_TO_GATEWAY);
				c.params.put(ArgKey.ROUTE_TO_GATEWAY_ALIAS, requestedRoomName);
				c.params.put(ArgKey.ROUTE_TO_GATEWAY_CALLING_EP_ID,
						request.getCallingEndpointId());
				c.params.put(ArgKey.ROUTE_TO_GATEWAY_CALL_REF,
						request.getCallRef());
				c.params.put(ArgKey.ROUTE_TO_GATEWAY_IP, optimalMCU.ip);
				c.params.put(ArgKey.ROUTE_TO_GATEWAY_PORT, optimalMCU.port);
			//}
		}
		result.add(c);
		return result;
	}

	private Mcu getOptimalMCU(Set<Mcu> mcus) {
		Mcu optimalMCU = null;
		// TODO: move to MCU / parameter?
		int maximalLoad = 2;
		int load = 10000;
		for (Mcu mcu : mcus) {
			// if (optimalMCU != null) {
			// }
			//System.out.println("LOADL: " + load);
			if (load > mcu.getLoad()) {
				optimalMCU = mcu;
			}
			load = optimalMCU.getLoad();
		}
		if (optimalMCU.getLoad() + 1 > maximalLoad) {
			optimalMCU = null;
		}
		return optimalMCU;
	}

}
